package study.Config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import study.Handle.MyAccessDeniedHandle;
import study.Handle.MyAuthenticationFailureHandler;
import study.Handle.MyAuthenticationSuccessHandler;
import study.Utils.Security.UserDetailsServiceImpl;

import javax.sql.DataSource;

/**
 * SpringSecurity 配置类
 * 运行流程:访问该资源需不需要登录→登录→需不需要某种权限→验证→报错:500
 * @author Ychiha
 * @version 1.0
 * @date 11/1/2022 上午9:33
 */
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAccessDeniedHandle myAccessDeniedHandle;  //自定义处理器

    @Autowired
    private UserDetailsServiceImpl userDetailsService;  //自定义登录逻辑

    @Autowired
    private DataSource dataSource; //sql下的DataSource

    @Autowired
    private PersistentTokenRepository persistentTokenRepository; //Token持久的存储仓库 使用了内存的方式存储

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //表单提交(配置没有前后顺序)
        http.formLogin()
                //定义 表单表格的路径(form action属性值)为/login ，以保证执行UserDetailsServiceImpl.java中的方法
                .loginProcessingUrl("/login")
                //自定义登录页面
                .loginPage("/login.html")
                //.loginPage("/toview");

                /**
                 * 前后端不分离情况下，设置跳转页面
                 */
                //登录成功跳转到的页面(必须为POST请求)
//                .successForwardUrl("/tomain")
                //登录失败跳转到的页面(必须为POST请求)
//                .failureForwardUrl("/toerror")

                /**
                 * 前后端分离的情况下设置跳转页面
                 * 不能和successForwardUrl共存
                 */
//                .successHandler(new MyAuthenticationSuccessHandler("http://localhost:8081/#/pages/login/login"))
                .successHandler(new MyAuthenticationSuccessHandler("http://127.0.0.1:8080/main.html"))
                .failureHandler(new MyAuthenticationFailureHandler("https://www.bilibili.com/video/av2831981/?p=12"))
                //自定义请求参数名
                //配置表单提交的账号name为springUser
                .usernameParameter("springUser")
                //配置表单提交的密码name为springPassword
                .passwordParameter("springPassword");


        //相当于拦截器
        http.authorizeRequests()

                /**
                 * 给登录页面、失败页面、静态资源等放行 (需要记得，引用的js、css文件都需要放行)
                 * ?  匹配一个字符
                 * *  匹配0个或多个字符
                 * ** 匹配0个或多个目录
                 */

                //写法1
//                .antMatchers("/login.html").permitAll()
//                .antMatchers("/error.html").permitAll()

                //写法2
//                .antMatchers("/login.html","/error.html").permitAll()

                //写法3
                .antMatchers("/logi?.html","/err*").permitAll()
                //.antMatchers("http://localhost:8081/#/pages/login/login").permitAll()
                //.antMatchers("/toview").permitAll()

                //写法4 access方法(所有跟在.antMatchers后面的方法都可以采取access写法)
//                .antMatchers("/login.html","/error.html").access("permitAll()")

                //给静态资源放行
                .antMatchers("/js/*","/image/*").permitAll()
//                .antMatchers(("**/*.png")).permitAll()  //所有目录下，后缀为png的都给放行
//                .regexMatchers(".+[.]png").permitAll()  // .任意+[.]至少一个. png  与上一行效果一致
                .regexMatchers(HttpMethod.POST,"login").permitAll() //规定好必须为Post请求
                .mvcMatchers("/login.html").servletPath("/yzm").permitAll()

                /**
                 * 根据权限进行放行
                 */

                //判断用户认证的账号是否拥有Vip权限-有则放行
                .antMatchers("/vip.html").hasAnyAuthority("Vip")    // hasAnyAuthority-严格区分大小写

                //判断用户认证的账号是否具有角色yzm权限-有则放行
                .antMatchers("/yzm.html").hasRole("yzm")    //hasRole 不能以"ROLE_"作为前缀,严格区分大小写
//                .antMatchers("/yzm.html").hasAnyRole("yzm,cyt") //hasAnyRole 判断用户是否有其中一个角色，有则放行

                //根据IP地址放行 (场景:只给后台访问)
//                .antMatchers("/yzm.html").hasIpAddress("127.0.0.1")

                //配置权限控制类，以便用access调用
//                .anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");

                //所有请求都必须被认证,必须登录之后被访问
                .anyRequest().authenticated();

        //csrf关闭防火墙(正规条件下，需要在登陆页面存放csrf的token和表单一起提交到后台)
//        http.csrf().disable();

        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandle);

        //记住我功能 底层为jdbc
        http.rememberMe()
                //设置失效时间 单位:秒 默认为2周
//                .tokenValiditySeconds(60)
//                .rememberMeParameter("remember-me") //用来更改表单中的参数
                //自定义登录逻辑
                .userDetailsService(userDetailsService)
                //持久层对象
                .tokenRepository(persistentTokenRepository);

        //注销
        http.logout()
                //自定义退出请求的路径
                .logoutUrl("/app/exit")  //退出请求的地址要求与此String相同 (默认为logout)
                //退出登录跳转页面 在当前的项目下寻找html
                .logoutSuccessUrl("/login.html");

    }

    /**
     * 注入PasswordEncoder实例
     * @return
     */
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 通过jdbc存储token以达到 "记住我"的功能
     * @return
     */
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){

        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);

        //自动建表，第一次启动时需要，第二次启动需要注释掉 否则报错
//        jdbcTokenRepository.setCreateTableOnStartup(true);

        return jdbcTokenRepository;

    }
}
